import { defineComponent, ExtractPropTypes, ref, getCurrentInstance, inject, computed, cloneVNode, PropType, onBeforeUnmount } from 'vue'
import Schema from 'async-validator'
// import { get, set, cloneDeep, castArray } from 'lodash-es'
import { get, set, cloneDeep, castArray } from '../_util/_'

import { getComponent, flattenChildren, isValidElement, findDOMNode } from '../_util/props-util'
import { FormContextKey, FormItemRule, FormItemValidateOptions } from './interface'
import { FormProps } from './Form'

const formItemProps = {
  prefixCls: { default: 'x-form-item' },
  layout: String as PropType<'horizontal' | 'vertical'>,
  label: String,
  labelWidth: Number,
  prop: String,
  rules: [Object, Array] as PropType<FormItemRule | FormItemRule[]>,
  required: Boolean,
  first: Boolean,
  extra: String
}

export type FormItemProps = ExtractPropTypes<typeof formItemProps>

export default defineComponent({
  name: 'x-form-item',
  props: formItemProps,
  setup(props, { expose, slots }) {
    const { proxy: vm } = getCurrentInstance()

    const formContext = inject(FormContextKey)
    const formProps = formContext?.props || ({} as FormProps)

    const formRulesRef = computed<FormItemRule[]>(() => castArray(get(formProps.rules, props.prop) || []))
    const rulesRef = computed(() => {
      const rules = formRulesRef.value.concat(props.rules || [])
      if (props.required) rules.push({ required: true, trigger: 'change', type: 'any' })
      return rules
    })
    const valueRef = computed(() => get(formProps.model, props.prop))
    const initialValue = cloneDeep(valueRef.value)

    const labelWidthRef = computed(() => props.labelWidth ?? formProps.labelWidth ?? undefined)
    const layoutRef = computed(() => props.layout || formProps.layout)
    const requiredRef = computed(() => rulesRef.value.some(rule => rule.required))

    const explainsRef = ref<string[]>([])

    const labelNodeRef = computed(() => {
      const layout = layoutRef.value
      const child = getComponent(vm, 'label')
      if (layout === 'vertical' && !child) {
        return undefined
      }
      const { prefixCls } = props
      const labelProps = {
        class: `${prefixCls}_label`,
        style: { width: labelWidthRef.value + 'px' }
      }
      return <label {...labelProps}>{getComponent(vm, 'label')}</label>
    })

    const exposeProps = {
      props,
      valueRef,
      validate,
      resetField,
      clearValidate
    }
    if (formContext) {
      formContext.addItem(exposeProps)
      onBeforeUnmount(() => {
        formContext.delItem(exposeProps)
      })
    }

    function validate(options: FormItemValidateOptions = {}) {
      const { prop, first } = props
      if (prop == null) return Promise.resolve()
      if (options.first === undefined) options.first = first

      const trigger = options.trigger
      let rules = rulesRef.value
      if (trigger) rules = rules.filter(rule => rule.trigger?.includes(trigger))
      if (!rules.length) {
        return Promise.resolve()
      }

      const validator = new Schema({ [prop]: rules })
      return new Promise((resolve, reject) => {
        validator.validate({ [prop]: valueRef.value }, options, errors => {
          if (errors?.length) {
            explainsRef.value = errors.map(e => e.message)
            reject({ errors })
          } else {
            clearValidate()
            resolve({})
          }
        })
      })
    }
    function resetField() {
      set(formProps.model, props.prop, cloneDeep(initialValue))
      clearValidate()
    }
    function clearValidate() {
      explainsRef.value = []
    }

    expose(exposeProps)

    function onFocus() {
      validate({ trigger: 'focus' })
    }
    function onBlur() {
      validate({ trigger: 'blur' })
    }
    function onChange() {
      clearValidate()
      validate({ trigger: 'change' })
    }

    return () => {
      const { prefixCls } = props

      const cls = [
        prefixCls,
        `${prefixCls}-${layoutRef.value}`,
        {
          [`${prefixCls}-error`]: explainsRef.value.length,
          [`${prefixCls}-required`]: requiredRef.value
        }
      ]

      const children = flattenChildren(slots.default?.())
      let child = children[0]
      if (isValidElement(child)) {
        child = cloneVNode(child, { onFocus, onBlur, onChange })
      }

      return (
        <div class={cls}>
          {labelNodeRef.value}

          <div class={`${prefixCls}_wrapper`}>
            <div class={`${prefixCls}_input`}>{[child, children.slice(1)]}</div>

            <div class={`${prefixCls}_feedback`}>
              {explainsRef.value.map(msg => (
                <div class={`${prefixCls}_feedback_line`}>{msg}</div>
              ))}
              {props.extra ? <div class={`${prefixCls}_extra`}>{getComponent(vm, 'extra')}</div> : undefined}
            </div>
          </div>
        </div>
      )
    }
  }
})
